package com.cloudbroker.bcs.common.server;

import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

import com.cloudbroker.bcs.common.util.ValueUtil;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jetty.jmx.ConnectorServer;
import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.proxy.ProxyServlet;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.webapp.WebAppContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.cloudbroker.bcs.common.util.HSBCSProperties;
import com.cloudbroker.bcs.common.util.IOUtil;

import javax.management.remote.JMXServiceURL;

public class JettyServer {
    
    private static final String PROP_NAME__PREFIX = HSBCSProperties.PROP_NAME_PREFIX + "jetty.";
    
    private static final String PROP_NAME_CONTEXT_PATH = PROP_NAME__PREFIX + "contextPath";
    
    private static final String PROP_NAME_WEBAPP_PATH = PROP_NAME__PREFIX + "webappPath";
    
    private static final String PROP_NAME_HOST = PROP_NAME__PREFIX + "host";
    
    private static final String PROP_NAME_PORT = PROP_NAME__PREFIX + "port";

    private static final java.lang.String PROP_NAME_JMX_ENABLE = HSBCSProperties.PROP_NAME_PREFIX + "jmx.enable";
    
    private static final Logger LOGGER = LoggerFactory.getLogger(JettyServer.class);
    
    private static final String DEFAULT_CHARSET = "UTF-8";

    private Boolean jmxEnable;

    private String contextPath;
    
    private String webappPath;
    
    private InetSocketAddress address;


    
    private Server server;
    
    private WebAppContext webapp;
    
    public static void main(String[] args) throws Exception {
        String contextPath = System.getProperty(PROP_NAME_CONTEXT_PATH);
        if (StringUtils.isBlank(contextPath)) {
            contextPath = HSBCSProperties.getProperty(PROP_NAME_CONTEXT_PATH);
        }
        
        String webappPath = System.getProperty(PROP_NAME_WEBAPP_PATH);
        if (StringUtils.isBlank(webappPath)) {
            webappPath = HSBCSProperties.getProperty(PROP_NAME_WEBAPP_PATH);
        }
        
        String host = System.getProperty(PROP_NAME_HOST);
        if (StringUtils.isBlank(host)) {
            host = HSBCSProperties.getProperty(PROP_NAME_HOST);
        }

        boolean jmxEnable = ValueUtil.getBoolean(System.getProperty(PROP_NAME_JMX_ENABLE) , false);
        if( !jmxEnable ) {
            jmxEnable = ValueUtil.getBoolean(HSBCSProperties.getProperty(PROP_NAME_JMX_ENABLE) , false);
        }
        
        String portStr = System.getProperty(PROP_NAME_PORT);
        if (StringUtils.isBlank(portStr)) {
            portStr = HSBCSProperties.getProperty(PROP_NAME_PORT);
        }
        if (StringUtils.isBlank(portStr)) {
            throw new IllegalArgumentException(PROP_NAME_PORT + " is required");
        }
        int port = 0;
        try {
            port = Integer.parseInt(portStr);
        } catch (Exception e) {
            e.printStackTrace();
            throw new IllegalArgumentException(PROP_NAME_PORT + " should be an integer between 1 and 65535");
        }
        JettyServer server = null;
        if (StringUtils.isBlank(host)) {
            server = new JettyServer(contextPath, webappPath, port);
        } else {
            server = new JettyServer(contextPath, webappPath, host, port);
        }
        server.setJmxEnable(jmxEnable);
        server.start();
    }
    
    public JettyServer(String contextPath, String webappPath, int port) {
        this(contextPath, webappPath, new InetSocketAddress(port));
    }
    
    public JettyServer(String contextPath, String webappPath, String hostname, int port) {
        this(contextPath, webappPath, new InetSocketAddress(hostname, port));
    }
    
    public JettyServer(String contextPath, String webappPath, InetSocketAddress address) {
        if (StringUtils.isBlank(contextPath)) {
            contextPath = "";
        }
        this.contextPath = contextPath;
        if (StringUtils.isBlank(webappPath)) {
            throw new IllegalArgumentException("webappPath is required");
        }
        this.webappPath = webappPath;
        this.address = address;
    }
    
    public void start() throws Exception {
        if (null == server || server.isStopped()) {
            doStart();
        } else {
            throw new IllegalStateException("JettyServer already started.");
        }
    }
    
    private void doStart() throws Exception {
        if (!checkServerAddressAvailable()) {
            throw new IllegalStateException("Server address already in use: " + address);
        }
        
        ensureTmpDir();
        
        System.setProperty("org.eclipse.jetty.util.URI.charset", DEFAULT_CHARSET);
        System.setProperty("org.eclipse.jetty.util.log.class", "org.eclipse.jetty.util.log.StdErrLog");
        System.setProperty("org.eclipse.jetty.server.Request.maxFormContentSize", "600000");
        
        webapp = new WebAppContext(webappPath, contextPath);

        server = new Server(address);

        server.setHandler(webapp);

        if( jmxEnable ) {
            supportJMX();
        }

        //新增透明代理
        initTransparentProxy();

        long st = System.currentTimeMillis();
        server.start();
        long sp = System.currentTimeMillis() - st;
        System.out.println("JettyServer started: " + String.format("%.2f sec", sp / 1000D));

        server.join();
    }

    private void supportJMX() {
        MBeanContainer mbContainer=new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
        server.addEventListener(mbContainer);
        server.addBean(mbContainer);
        server.addBean(Log.getLog());
    }

    private void ensureTmpDir() {
        String tmpDir = System.getProperty("java.io.tmpdir");
        IOUtil.ensureDir(new File(tmpDir));
    }
    
    private boolean checkServerAddressAvailable() {
        int port = address.getPort();
        if (0 < port) {
            ServerSocket ss = null;
            try {
                ss = new ServerSocket(port, 0, address.getAddress());
            } catch (Exception e) {
                LOGGER.error("check serverAddress failed", e);
                return false;
            } finally {
                if (null != ss) {
                    try {
                        ss.close();
                    } catch (IOException e) {
                        LOGGER.error("close ServerSocket failed", e);
                    }
                }
            }
        } else {
            throw new IllegalArgumentException("Invalid port " + port);
        }
        return true;
    }


    /**
     * 初始化透明代理
     */
    private void initTransparentProxy(){
        //透明代理
        Map<String,String> pattern = HSBCSProperties.getPropertySuffix("_open.url.pattern");
        Map<String,String> tourl = HSBCSProperties.getPropertySuffix("_open.proxy.tourl");
        Map<String,String> prefix = HSBCSProperties.getPropertySuffix("_open.proxy.prefix");
        Map<String,String> rewriteMap  = new HashMap<>();
        rewriteMap.putAll(pattern);
        rewriteMap.putAll(tourl);
        rewriteMap.putAll(prefix);
        ServletHandler servletHandler=webapp.getServletHandler();

        for (String s : rewriteMap.keySet()) {
            if(s.endsWith("_open.url.pattern")){
                Pattern r = Pattern.compile(rewriteMap.get(s));
                // 现在创建 matcher 对象
                String target_id = s.split("\\.")[0];
                ServletHolder proxy=new ServletHolder(ProxyServlet.Transparent.class);
                servletHandler.addServletWithMapping(proxy,  rewriteMap.get(target_id+".url.pattern"));
                proxy.setInitParameter("proxyTo",rewriteMap.get(target_id+".proxy.tourl"));
                proxy.setInitParameter("prefix",  rewriteMap.get(target_id+".proxy.prefix"));
            }
        }
    }

    public Boolean getJmxEnable() {
        return jmxEnable;
    }

    public void setJmxEnable(Boolean jmxEnable) {
        this.jmxEnable = jmxEnable;
    }
}
